package com.hzit.order.service.impl;

import cn.hutool.core.bean.BeanUtil;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.hzit.order.dto.ItemDTO;
import com.hzit.order.dto.OrderDTO;
import com.hzit.order.entity.Order;
import com.hzit.order.entity.OrderItem;
import com.hzit.order.entity.PayLog;
import com.hzit.order.interceptor.LoginInterceptor;
import com.hzit.order.mapper.OrderMapper;
import com.hzit.order.service.IOrderItemService;
import com.hzit.order.service.IOrderService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hzit.order.service.IPayLogService;
import com.hzit.order.vo.OrderVo;
import com.ruoyi.common.core.constant.Constants;

import com.ruoyi.common.core.exception.OrderRepeatSubmitException;
import com.ruoyi.common.core.utils.IdWorker;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.stereotype.Service;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.math.BigDecimal;
import java.nio.channels.ReadPendingException;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author wf
 * @since 2024-02-27
 */
@Service
public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements IOrderService {
	@Autowired
	private StringRedisTemplate redisTemplate;
	@Autowired
	private IOrderItemService orderItemService;
	@Autowired
	private IdWorker idWorker;
	@Autowired
	private ThreadPoolExecutor executor;
	@Autowired
	private IPayLogService payLogService;

	//1. 保存订单
	@Override
	public void saveOrder(OrderVo vo) throws ExecutionException, InterruptedException {
		//1.1 因为要比较redis中的token与表单隐藏域中的token是否相等，我们可以使用lua脚本，将多个redis命令看作一个原子命令来执行
		String lua = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
		RedisScript<Long> script = new DefaultRedisScript<>(lua,Long.class);
		List<String> key = Collections.singletonList(Constants.ORDER_TOKEN_PREFIX + LoginInterceptor.local.get());
		//1.2 执行lua脚本，参数1：脚本 参数2：key代表redis中存放的token值（代表KEYS[1]） 参数3：代表ARGV[1]，就是表单隐藏域中放的token值
		Long result = redisTemplate.execute(script, key, vo.getToken());
		//1.3 如果返回值是0L,证明执行失败
		if(result == 0L){
			throw new OrderRepeatSubmitException("表单重复提交异常！");
		}
		//1.4 将订单及订单项分别放到两个集合中进行批量添加(准备订单及订单项数据)
		List<Order> orders = new ArrayList<>();             // 订单列表
		List<OrderItem> orderItems = new ArrayList<>();     // 订单项列表
		List<Long> orderIds = new ArrayList<>();           // 存放订单id
		BigDecimal orderSumMoney = getOrderAndOrderList(vo, orders, orderItems,orderIds);
		System.out.println("orderSumMoney = " + orderSumMoney);

		//1.5 进行异步编排添加数据
		//1.5.1 保存订单任务
		ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
		CompletableFuture<Void> saveOrderFuture = CompletableFuture.runAsync(() -> {
				RequestContextHolder.setRequestAttributes(requestAttributes);
				this.saveBatch(orders);
		}, executor);
		//1.5.2 保存订单项任务
		CompletableFuture<Void> saveOrderItemFuture = CompletableFuture.runAsync(() -> {
			RequestContextHolder.setRequestAttributes(requestAttributes);
			orderItemService.saveBatch(orderItems);
		}, executor);
		//1.5.3 保存支付日志
		CompletableFuture<Void> savePayLogFuture = saveOrderFuture.thenAcceptBothAsync(saveOrderItemFuture, (t, e) -> {
			RequestContextHolder.setRequestAttributes(requestAttributes);
			HttpServletRequest request = requestAttributes.getRequest();
			HttpSession session = request.getSession();
			Map map = (Map) session.getAttribute("userInfo");
			//① 定义支付日志对象
			PayLog payLog = new PayLog();
			payLog.setOutTradeNo(idWorker.nextId() + "");
			payLog.setTradeState("0");      // 0: 未支付 1： 己支付
			payLog.setOrderList(orderIds.toString().replace("[", "").replace("]", ""));
			payLog.setPayType(vo.getPaymentType()); // 支付类型
			payLog.setTotalFee(orderSumMoney.longValue());  // 订单总金额
			payLog.setCreateTime(LocalDateTime.now());      // 订单创建时间
			payLog.setUserId(map.get("login") + "");
			//② 保存到数据库中
			payLogService.save(payLog);
			//③ 保存到redis中
			redisTemplate.opsForValue().set(Constants.ORDER_PAY_LOG_PREFIX + map.get("login") + "", JSON.toJSONString(payLog));
		});

		CompletableFuture.allOf(saveOrderFuture,saveOrderItemFuture,savePayLogFuture).get();
		// todo...
		// 最后，删除redis中保存的购物车信息（删除购物车条件：数据量达到300条，或者存放时间超过1周）
	}
	// 根据登录用户找订单及订单项列表
	@Override
	public List<OrderDTO> findOrderListByLogin(String login) {
		//1. 得到用户关联的订单列表
		List<Order> orderList = this.list(new QueryWrapper<Order>().eq("user_id", login));
		//2. 将List<Order>转换为List<OrderDTO>
		List<OrderDTO> orderDTOS = orderList.stream().map(m -> {
			OrderDTO dto = new OrderDTO();
			BeanUtil.copyProperties(m, dto, true);
			return dto;
		}).collect(Collectors.toList());
		//3. 遍历列表找到某个订单关联的订单项列表
		for (OrderDTO dto : orderDTOS) {
			//3.1 查询订单关联的订单项集合
			List<OrderItem> orderItemList = orderItemService.list(new QueryWrapper<OrderItem>().eq("order_id", dto.getOrderId()));
			//3.2 为dto设置关联的订单项集合
			dto.setOrderItems(orderItemList);
		}
		//4. 最后，返回List<OrderDTO>
		return orderDTOS;
	}

	// 将订单及订单项数组整理到各自集合中
	private BigDecimal getOrderAndOrderList(OrderVo vo, List<Order> orders, List<OrderItem> orderItems,List<Long> orderIds) {
		final BigDecimal[] orderTotalMoney = {new BigDecimal(0)};
		//1.3 取得购物车数组
		Map<String, List<ItemDTO>> cartListMap = findCartList();    // key：商家名字，value：对应商家的商品项列表
		//Collection<List<ItemDTO>> values = cartListMap.values();    // 集合的个数大小由商家名字个数决定
		cartListMap.forEach((seller,itemList) ->{
			//定义代表订单总金额的变量
			BigDecimal sum = new BigDecimal(0);     // 订单项的小计的累加值
			//1.3.1 构造一个订单
			Order order = new Order();
			//1.3.2 将vo中对应属性复制给order
			BeanUtil.copyProperties(vo,order,true);
			//1.3.3 生成一个随机id
			long orderId = idWorker.nextId();
			//1.3.4 将订单编号放到编号集合中
			orderIds.add(orderId);
			order.setOrderId(orderId);
			order.setStatus("1");       // 状态：1、未付款，2、已付款，3、未发货，4、已发货，5、交易成功，6、交易关闭,7、待评价
			order.setUserId(LoginInterceptor.login.get());  // 登录userId
			order.setCreateTime(LocalDateTime.now());
			order.setSellerId(seller);  // 商家名字
			order.setSourceType("2");   // 订单来源：1:app端，2：pc端，3：M端，4：微信端，5：手机qq端
			order.setUpdateTime(LocalDateTime.now());

			//1.4 遍历订单项，将其放到items集合中
			for (ItemDTO dto : itemList) {
				OrderItem orderItem = new OrderItem();
				orderItem.setOrderId(orderId);
				orderItem.setId(idWorker.nextId());
				orderItem.setPicPath(dto.getImage());
				orderItem.setItemId(dto.getId());
				orderItem.setGoodsId(dto.getGoodsId());
				orderItem.setSellerId(seller);
				orderItem.setPrice(dto.getPrice());
				orderItem.setNum(dto.getBuyNum());
				BigDecimal num = new BigDecimal(orderItem.getNum());
				// 计算小计
				BigDecimal total = orderItem.getPrice().multiply(num);
				// 累加订单的总金额
				sum = sum.add(total);
				// 添加订单项到订单项列表中
				orderItems.add(orderItem);
				// 扣减库存 (应该在这里做才合理，前面我们是在添加购物车时扣减的，不是特别合理)
				//todo...

			}
			// 设置订单总金额
			order.setPayment(sum);
			// 添加订单到订单集合中
			orders.add(order);
			// 累加订单总金额
			orderTotalMoney[0] = orderTotalMoney[0].add(sum);
		});
		return orderTotalMoney[0];
	}

	// 获取到购物车数据，并以商家名字进行分组
	public Map<String,List<ItemDTO>> findCartList(){
		//1. 得到购物车中所有的数据
		List<Object> values = redisTemplate.opsForHash().values(Constants.CART_PREFIX + LoginInterceptor.local.get());
		//2. 转换为我们想要的数据
		return   values.stream()
				.map(m -> JSON.parseObject(m.toString(), ItemDTO.class))
				.collect(Collectors.toList())
				.stream()
				.collect(Collectors.groupingBy(ItemDTO::getSeller));
	}
}
